#include "EGL/EGLAdaptor.h"

#pragma comment(lib, "../Lib/libEGL.lib")

namespace Nezha
{
	// We should say thank you to Torus Knot Software.
	class EGLConfigAttribs
	{
	public:
		typedef PMap<int, int> IntFields;
		IntFields mFields;

		EGLConfigAttribs(const int* attribs)
		{
			mFields[EGL_CONFIG_CAVEAT] = EGL_NONE;

			for (int i = 0; attribs[2*i] != EGL_NONE; i++)
			{
				mFields[attribs[2*i]] = attribs[2*i+1];
			}
		}

		void load(EGLConfig glConfig, EGLDisplay display)
		{
			std::map<int,int>::iterator it;

			for (it = mFields.begin(); it != mFields.end(); it++)
			{
				it->second = EGL_NONE;

				eglGetConfigAttrib(display, glConfig, it->first, &it->second);
			}
		}

		bool operator>(EGLConfigAttribs& alternative)
		{
			// Caveats are best avoided, but might be needed for anti-aliasing
			if (mFields[EGL_CONFIG_CAVEAT] != alternative.mFields[EGL_CONFIG_CAVEAT])
			{
				if (mFields[EGL_CONFIG_CAVEAT] == EGL_SLOW_CONFIG)
				{
					return false;
				}

				if (mFields.find(EGL_SAMPLES) != mFields.end() &&
					mFields[EGL_SAMPLES] < alternative.mFields[EGL_SAMPLES])
				{
					return false;
				}
			}

			std::map<int,int>::iterator it;

			for (it = mFields.begin(); it != mFields.end(); it++)
			{
				if (it->first != EGL_CONFIG_CAVEAT &&
					mFields[it->first] > alternative.mFields[it->first])
				{
					return true;
				}
			}

			return false;
		}
	};

	EGLAdaptor::EGLAdaptor()
		:mhWnd(NULL)
		//,mCurrentContext(NULL)
		,mNativeDisplay(NULL)
		,mGLDisplay(NULL)
		,mMainEGLContext(NULL)
		,mMainSurface(NULL)
		,mEGLConfig(NULL)
	{

	}

	EGLAdaptor::~EGLAdaptor()
	{

	}

	void EGLAdaptor::_initialize(NezhaEngine* engine)
	{
		mEngine = engine;

		// internal access.
		EngineConfig* cfg = _getEngineConfig();

		//// chose configs
		//EGLint* attribList = (EGLint*)NZMalloc(sizeof(GLint) * 2 * 32);
		//int cursor = 0;

		//attribList[cursor] = EGL_RED_SIZE;
		//attribList[cursor + 2] = EGL_GREEN_SIZE;
		//attribList[cursor + 4] = EGL_BLUE_SIZE;
		//attribList[cursor + 6] = EGL_ALPHA_SIZE;

		//if(cfg->mColorBufferBits == 16)
		//{
		//	attribList[cursor + 1] = 5;
		//	attribList[cursor + 3] = 6;
		//	attribList[cursor + 5] = 5;
		//	attribList[cursor + 7] = EGL_DONT_CARE;
		//}
		//else if(cfg->mColorBufferBits == 24)
		//{
		//	attribList[cursor + 1] = 5;
		//	attribList[cursor + 3] = 6;
		//	attribList[cursor + 5] = 5;
		//	attribList[cursor + 7] = 8;
		//}
		//else if(cfg->mColorBufferBits == 32)
		//{
		//	attribList[cursor + 1] = 8;
		//	attribList[cursor + 3] = 8;
		//	attribList[cursor + 5] = 8;
		//	attribList[cursor + 7] = 8;
		//}

		//cursor += 8;

		//GLint maxSupported = 0;

		//glGetIntegerv(GL_DEPTH_BITS, &maxSupported);
		//attribList[cursor] = EGL_DEPTH_SIZE;
		//attribList[cursor + 1] = maxSupported;

		//glGetIntegerv(GL_STENCIL_BUFFER_BIT, &maxSupported);
		//attribList[cursor + 2] = EGL_STENCIL_SIZE;
		//attribList[cursor + 3] = maxSupported;

		if(!cfg->mWindow)
		{
			mhWnd = (EGLNativeWindowType)createWindow(*cfg);
		}
		else
		{
			EGLint major = 0, minor = 0;
			mhWnd = (EGLNativeWindowType)cfg->mWindow;
			mNativeDisplay = InternalGetNativeDisplay(mhWnd);
			mGLDisplay = eglGetDisplay(mNativeDisplay);

			if(eglInitialize(mGLDisplay, &major, &minor) == EGL_FALSE)
			{
				NEZHA_EXCEPTION(NZException::InvalidState, "Couldn't initialize EGLDisplay from default native display.");
			}
		}

		int minAttribs[] = {
			EGL_LEVEL, 0,
			EGL_DEPTH_SIZE, 16,
			EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
			EGL_NONE
		};

		int maxAttribs[] = {
			EGL_SAMPLES, cfg->mAntiAliasLevel,
			EGL_STENCIL_SIZE, INT_MAX,
			EGL_NONE
		};

		mEGLConfig = selectGLConfig(minAttribs, maxAttribs);

		EGLint contextAttribs[] = {
			EGL_CONTEXT_CLIENT_VERSION, 2,
			EGL_NONE, EGL_NONE
		};

		mMainSurface = eglCreateWindowSurface(mGLDisplay, mEGLConfig, mhWnd, NULL);

		if(mMainSurface == EGL_NO_SURFACE)
		{
			NEZHA_EXCEPTION(NZException::InvalidState, "Failed to create EGL surface.");
		}

		mMainEGLContext = eglCreateContext(mGLDisplay, mEGLConfig, EGL_NO_CONTEXT, contextAttribs);

		if(mMainSurface == EGL_NO_CONTEXT)
		{
			NEZHA_EXCEPTION(NZException::InvalidState, "Failed to create EGL context.");
		}

		if(!eglMakeCurrent(mGLDisplay, mMainSurface, mMainSurface, mMainEGLContext))
		{
			NEZHA_EXCEPTION(NZException::InvalidState, "Failed to set current context.");
		}

		if(!cfg->mWindowed)
		{
			// TODO
		}
	}

	void EGLAdaptor::_uninitialize()
	{
		if(mMainSurface)
		{
			eglDestroySurface(mGLDisplay, mMainSurface);
			mMainSurface = NULL;
		}

		if(mMainEGLContext)
		{
			eglDestroyContext(mGLDisplay, mMainEGLContext);
			mMainEGLContext = NULL;
		}

		if(mGLDisplay)
		{
			eglTerminate(mGLDisplay);
			mGLDisplay = NULL;
		}
	}

	EGLConfig EGLAdaptor::selectGLConfig(const int* minAttribs, const int* maxAttribs)
	{
		EGLConfig* glCfgs;
		int num_cfgs = 0;
		
		glCfgs = chooseNCreateGLConfig(minAttribs, &num_cfgs);

		if(num_cfgs == 0)
		{
			glCfgs = createConfigs(&num_cfgs);
		}

		if(!glCfgs)
		{
			return NULL;
		}

		EGLConfig glConfig = glCfgs[0];

		if(maxAttribs)
		{
			EGLConfigAttribs maximum(maxAttribs);
			EGLConfigAttribs best(maxAttribs);
			EGLConfigAttribs candidate(maxAttribs);

			best.load(glConfig, mGLDisplay);

			for(int i = 1; i < num_cfgs; i++)
			{
				candidate.load(glCfgs[i], mGLDisplay);

				if(candidate > maximum)
				{
					continue;
				}

				if(candidate > best)
				{
					glConfig = glCfgs[i];
					best.load(glConfig, mGLDisplay);
				}
			}
		}

		SystemFree(glCfgs);
		return glConfig;
	}

	EGLConfig* EGLAdaptor::createConfigs(int* num_elems)
	{
		EGLConfig* cfgs;

		if(eglGetConfigs(mGLDisplay, NULL, 0, num_elems) == EGL_FALSE)
		{
			IMPL_ERROR_DUMP(this);
			*num_elems = 0;
			return NULL;
		}

		cfgs = (EGLConfig*)SystemMalloc(*num_elems * sizeof(EGLConfig));

		if(eglGetConfigs(mGLDisplay, cfgs, *num_elems, num_elems) == EGL_FALSE)
		{
			*num_elems = 0;
			IMPL_ERROR_DUMP(this);
			SystemFree(cfgs);
			return NULL;
		}

		return cfgs;
	}

	EGLConfig* EGLAdaptor::chooseNCreateGLConfig(const int* arrtibs, int* elems)
	{
		EGLConfig* cfg;

		eglGetConfigs(mGLDisplay, NULL, 0, elems);

		if(eglChooseConfig(mGLDisplay, arrtibs, NULL, 0, elems) == EGL_FALSE)
		{
			IMPL_ERROR_DUMP(this);
			return NULL;
		}

		cfg = (EGLConfig*)SystemMalloc(*elems * sizeof(EGLConfig));
		EGLint numElems = *elems;
		if(eglChooseConfig(mGLDisplay, arrtibs, cfg, numElems, elems) == EGL_FALSE)
		{
			IMPL_ERROR_DUMP(this);
			SystemFree(cfg);

			return NULL;
		}

		return cfg;
	}

	void EGLAdaptor::setCurrentContext(NZ_GLESContext* context)
	{
		// this is dirty. but simple.
		NZ_GLESContext_EGL* egl_context = (NZ_GLESContext_EGL*)context;
		EGLBoolean ret = eglMakeCurrent(egl_context->mEGLDisplay, egl_context->mEGLSurface,
			egl_context->mEGLSurface, egl_context->mEGLContext);

		IMPL_ERROR_DUMP(this);

		//mCurrentContext = egl_context;
	}

	void EGLAdaptor::clearContext()
	{
		//if(mCurrentContext)
		{
			eglMakeCurrent(mGLDisplay, 0, 0, 0);
			IMPL_ERROR_DUMP(this);
		}

		//mCurrentContext = NULL;
	}

	void EGLAdaptor::releaseContext(NZ_GLESContext* context)
	{
		NZ_GLESContext_EGL* egl_context = (NZ_GLESContext_EGL*)context;
		eglDestroyContext(egl_context->mEGLDisplay, egl_context->mEGLContext);

		//if(egl_context == mCurrentContext)
		{
			clearContext();
		}
	}

	NZ_GLESContext* EGLAdaptor::createContext()
	{
		// TODO

		return NULL;
	}

	void EGLAdaptor::swapFrameBuffer(bool waitVSync)
	{
		//NZ_ASSERT(mCurrentContext);

		eglSwapBuffers(mGLDisplay, mMainSurface);

		GL_ERROR_DUMP(this);
	}

	void EGLAdaptor::checkImplError()
	{
#ifdef NEZHA_OPENGL_ERROR_CHECK

		int err = eglGetError();
		if(err != 0 && err != EGL_SUCCESS)
		{
			NZError("Report EGL error 0x%04X %s %d", err, GLESAdaptor::msErrorFileName, GLESAdaptor::msErrorLine);
		}
#endif
	}

}//end namespace Nezha